001    /*
002     * Copyright 2005 Stephen J. McConnell.
003     *
004     * Licensed  under the  Apache License,  Version 2.0  (the "License");
005     * you may not use  this file  except in  compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *   http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed  under the  License is distributed on an "AS IS" BASIS,
012     * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
013     * implied.
014     *
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package net.dpml.depot;
020    
021    import java.io.File;
022    import java.net.URL;
023    import java.net.URI;
024    import java.rmi.RMISecurityManager;
025    import java.util.ArrayList;
026    import java.util.Date;
027    
028    import net.dpml.transit.Disposable;
029    import net.dpml.transit.Transit;
030    import net.dpml.transit.TransitError;
031    import net.dpml.transit.DefaultTransitModel;
032    import net.dpml.transit.model.TransitModel;
033    import net.dpml.transit.monitor.Adapter;
034    import net.dpml.transit.monitor.LoggingAdapter;
035    import net.dpml.transit.monitor.RepositoryMonitorAdapter;
036    import net.dpml.transit.monitor.CacheMonitorAdapter;
037    import net.dpml.transit.monitor.NetworkMonitorAdapter;
038    
039    import net.dpml.lang.Enum;
040    import net.dpml.lang.PID;
041    import net.dpml.lang.Part;
042    
043    import net.dpml.util.Logger;
044    import net.dpml.util.PropertyResolver;
045    
046    /**
047     * CLI hander for the depot package.
048     *
049     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
050     * @version 1.0.1
051     */
052    public final class Main //implements ShutdownHandler
053    {
054        private static Main m_MAIN;
055        private static final PID PROCESS_ID = new PID();
056    
057        private Object m_plugin;
058        private boolean m_debug = false;
059        private boolean m_trace = false;
060        
061       /**
062        * Processes command line options to establish the command handler plugin to deploy.
063        * Command parameters recognixed by the console include the following:
064        * <ul>
065        *   <li>-Ddpml.depot.addplication=transit|station|metro|build</li>
066        *   <li>-debug</li>
067        * </ul>
068        * @param args the command line argument array
069        * @exception Exception if a error occurs
070        */
071        public static void main( String[] args )
072          throws Exception
073        {
074            if( null != m_MAIN )
075            {
076                final String error = 
077                  "Console already established.";
078                throw new IllegalArgumentException( error );
079            }
080            else
081            {
082                m_MAIN = new Main( args );
083            }
084        }
085        
086        private Main( String[] arguments )
087        {
088            String[] args = processSystemProperties( arguments );
089    
090            //
091            // check for debug and trace cli options
092            //
093            
094            if( CLIHelper.isOptionPresent( args, "-trace" ) )
095            {
096                args = CLIHelper.consolidate( args, "-trace" );
097                System.setProperty( "dpml.trace", "true" );
098                m_trace = true;
099            }
100            
101            if( CLIHelper.isOptionPresent( args, "-debug" ) )
102            {
103                args = CLIHelper.consolidate( args, "-debug" );
104                System.setProperty( "dpml.debug", "true" );
105                m_debug = true;
106            }
107            
108            //
109            // handle cli sub-system establishment
110            //
111            
112            Command command = getCommand( args );
113            if( Command.STATION.equals( command ) )
114            {
115                handleStation( args );
116            }
117            else
118            {
119                if( null == System.getProperty( "dpml.logging.config" ) )
120                {
121                    if( m_trace )
122                    {
123                        System.setProperty( "dpml.logging.config", "local:properties:dpml/transit/trace" );
124                    }
125                    else if( m_debug )
126                    {
127                        System.setProperty( "dpml.logging.config", "local:properties:dpml/transit/debug" );
128                    }
129                    else
130                    {
131                        System.setProperty( "dpml.logging.config", "local:properties:dpml/transit/default" );
132                    }
133                }
134            
135                if( m_debug || m_trace )
136                {
137                    for( int i=0; i<arguments.length; i++ )
138                    {
139                        getLogger().debug( "arg[" + i + "]: " + arguments[i] );
140                    }
141                }
142                
143                if( Command.BUILD.equals( command ) )
144                {
145                    handleBuild( args );
146                }
147                else if( Command.TRANSIT.equals( command ) )
148                {
149                    handleTransit( args );
150                }
151                else if( Command.METRO.equals( command ) )
152                {
153                    handleMetro( args );
154                }
155                else
156                {
157                    final String error = 
158                      "Missing application key '" + APPLICATION_KEY + "'.";
159                    System.err.println( error );
160                    System.exit( 1 );
161                }
162            }
163        }
164        
165        private void handleBuild( String[] args )
166        {
167            if( getLogger().isTraceEnabled() )
168            {
169                getLogger().trace( "launching builder" );
170            }
171            String name = "build";
172            String spec = "link:part:dpml/depot/dpml-library-build";
173            handlePlugin( name, spec, args, false );
174        }
175    
176        private void handleMetro( String[] args )
177        {
178            if( getLogger().isTraceEnabled() )
179            {
180                getLogger().trace( "launching metro" );
181            }
182            String name = "exec";
183            String spec = "link:part:dpml/station/dpml-station-exec";
184            handlePlugin( name, spec, args, true );
185        }
186    
187        private void handleTransit( String[] args )
188        {
189            String name = "transit";
190            String spec = "link:part:dpml/transit/dpml-transit-console";
191            handlePlugin( name, spec, args, false );
192        }
193    
194        private void handleStation( String[] args )
195        {
196            new File( Transit.DPML_DATA, "logs/station" ).mkdirs();
197            if( CLIHelper.isOptionPresent( args, "-server" ) )
198            {
199                if( m_trace )
200                {
201                    System.setProperty( "dpml.logging.level", "FINEST" );
202                }
203                else if( m_debug )
204                {
205                    System.setProperty( "dpml.logging.level", "FINE" );
206                }
207                if( getLogger().isTraceEnabled() )
208                {
209                    getLogger().trace( "launching station in server mode" );
210                }
211                String name = "station";
212                args = CLIHelper.consolidate( args, "-server" );
213                String spec = "link:part:dpml/station/dpml-station-server";
214                handlePlugin( name, spec, args, true );
215            }
216            else
217            {
218                if( getLogger().isTraceEnabled() )
219                {
220                    getLogger().trace( "launching station in client mode" );
221                }
222                String name = "station";
223                String spec = "link:part:dpml/station/dpml-station-console";
224                handlePlugin( name, spec, args, false );
225            }
226        }
227    
228        private void handlePlugin( String name, String spec, String[] args, boolean wait )
229        {
230            System.setSecurityManager( new RMISecurityManager() );
231            TransitModel model = getTransitModel( args );
232            boolean waitForCompletion = deployHandler( model, name, spec, args, wait );
233            if( !waitForCompletion )
234            {
235                if( m_plugin instanceof Disposable )
236                {
237                    Disposable disposable = (Disposable) m_plugin;
238                    disposable.dispose();
239                }
240                if( model instanceof DefaultTransitModel )
241                {
242                    DefaultTransitModel disposable = (DefaultTransitModel) model;
243                    disposable.dispose();
244                }
245                System.exit( 0 );
246            }
247        }
248        
249        private boolean deployHandler( 
250          TransitModel model, String command, String path, String[] args, boolean waitFor )
251        {
252            Logger logger = getLogger();
253            if( logger.isDebugEnabled() )
254            {
255                logger.debug( "date: " + new Date() );
256                logger.debug( "system: " + command );
257                logger.debug( "uri: " + path );
258                logger.debug( "args: [" + toString( args ) + "]" );
259                logger.debug( "system classloader: [" 
260                  + System.identityHashCode( ClassLoader.getSystemClassLoader() ) 
261                  + "]" );
262            }
263            Logger log = resolveLogger( logger, command );
264            try
265            {
266                URI uri = new URI( path );
267                Transit transit = Transit.getInstance( model );
268                setupMonitors( transit, (Adapter) logger );
269                
270                Part part = Part.load( uri, true );
271                m_plugin = 
272                  part.instantiate( 
273                    new Object[]
274                    {
275                        model, 
276                        args, 
277                        log
278                    }
279                  );
280            }
281            catch( GeneralException e )
282            {
283                getLogger().error( e.getMessage() );
284                System.exit( 1 );
285            }
286            catch( Exception e )
287            {
288                Throwable cause = e.getCause();
289                if( ( null != cause ) && ( cause instanceof GeneralException ) )
290                {
291                    getLogger().error( cause.getMessage() );
292                    System.exit( 1 );
293                }
294                else
295                {
296                    getLogger().error( e.getMessage(), e.getCause() );
297                    System.exit( 1 );
298                }
299            }
300            catch( Throwable e )
301            {
302                final String error = 
303                  "Deloyment failure." 
304                  + "\nTarget: " + command 
305                  + "\n   URI: " + path;
306                getLogger().error( error, e );
307                System.exit( 1 );
308            }
309            
310            if( m_plugin instanceof Runnable )
311            {
312                getLogger().debug( "starting " + m_plugin.getClass().getName() );
313                Thread thread = new Thread( (Runnable) m_plugin );
314                thread.start();
315                setShutdownHook( thread );
316                return true;
317            }
318            else
319            {
320                getLogger().debug( "deployed " + m_plugin.getClass().getName() );
321                return waitFor;
322            }
323        }
324        
325        private Logger resolveLogger( Logger logger, String command )
326        {
327            String partition = System.getProperty( "dpml.station.partition", null );
328            if( null != partition )
329            {
330                return new LoggingAdapter( partition );
331            }
332            else
333            {
334                return logger.getChildLogger( command );
335            }
336        }
337        
338        private TransitModel getTransitModel( String[] args )
339        {
340            final String key = "dpml.transit.model";
341            String property = null;
342            for( int i=0; i<args.length; i++ )
343            {
344                String arg = args[i];
345                if( arg.startsWith( "-D" + key + "=" ) )
346                {
347                    property = arg.substring( 21 );
348                    break;
349                }
350            }
351            
352            if( null != property )
353            {
354                if( property.startsWith( "registry:" ) )
355                {
356                    try
357                    {
358                        return (TransitModel) new URL( property ).getContent( 
359                          new Class[]{TransitModel.class} );
360                    }
361                    catch( Exception e )
362                    {
363                        final String error = 
364                          "Unable to resolve registry reference: " + property;
365                        throw new TransitError( error, e );
366                    }
367                }
368                else
369                {
370                    final String error = 
371                      "System property value for the key ': "
372                      + key 
373                      + "' contains an unrecognized value: "
374                      + property;
375                    throw new TransitError( error );
376                }
377            }
378            
379            //
380            // otherwise let Transit handle model creation
381            //
382            
383            try
384            {
385                Logger logger = getLogger().getChildLogger( "transit" );
386                return DefaultTransitModel.getDefaultModel( logger );
387            }
388            catch( Exception e )
389            {
390                final String error = 
391                  "Transit model establishment failure.";
392                throw new TransitError( error, e );
393            }
394        }
395        
396        private static Logger getLogger()
397        {
398            if( null == m_LOGGER )
399            {
400                String category = System.getProperty( "dpml.logging.category", "depot" );
401                m_LOGGER = new LoggingAdapter( java.util.logging.Logger.getLogger( category ) );
402            }
403            return m_LOGGER;
404        }
405    
406        private String toString( String[] args )
407        {
408            StringBuffer buffer = new StringBuffer();
409            for( int i=0; i<args.length; i++ )
410            {
411                if( i > 0 )
412                {
413                    buffer.append( ", " );
414                }
415                buffer.append( args[i] );
416            }
417            return buffer.toString();
418        }
419        
420        private String[] processSystemProperties( String[] args )
421        {
422            ArrayList result = new ArrayList();
423            for( int i=0; i < args.length; i++ )
424            {
425                String arg = args[i];
426                int index = arg.indexOf( "=" );
427                if( index > -1 && arg.startsWith( "-D" ) )
428                {
429                    String name = arg.substring( 2, index );
430                    String raw = arg.substring( index + 1 );
431                    String value = PropertyResolver.resolve( raw );
432                    System.setProperty( name, value );
433                }
434                else
435                {
436                    result.add( arg );
437                }
438            }
439            return (String[]) result.toArray( new String[0] );
440        }
441    
442        //--------------------------------------------------------------------------
443        // static utilities for setup of logging manager and root prefs
444        //--------------------------------------------------------------------------
445    
446       /**
447        * Setup the monitors.
448        */
449        private static void setupMonitors( Transit instance, Adapter adapter ) throws Exception
450        {
451            instance.getRepositoryMonitorRouter().addMonitor(
452              new RepositoryMonitorAdapter( adapter ) );
453            instance.getCacheMonitorRouter().addMonitor(
454              new CacheMonitorAdapter( adapter ) );
455            instance.getNetworkMonitorRouter().addMonitor(
456              new NetworkMonitorAdapter( adapter ) );
457        }
458        
459       /**
460        * Create a shutdown hook that will trigger shutdown of the supplied plugin.
461        * @param thread the application thread
462        */
463        public static void setShutdownHook( final Thread thread )
464        {
465            //
466            // Create a shutdown hook to trigger clean disposal of the
467            // controller
468            //
469            
470            Runtime.getRuntime().addShutdownHook(
471              new Thread()
472              {
473                  public void run()
474                  {
475                      try
476                      {
477                          thread.interrupt();
478                      }
479                      catch( Throwable e )
480                      {
481                          boolean ignorable = true;
482                      }
483                      System.runFinalization();
484                  }
485              }
486            );
487        }
488    
489       /**
490        * DPML build key.
491        */
492        private static final String BUILD_KEY = "dpml.build";
493    
494       /**
495        * The Depot system version.
496        */
497        private static final String BUILD_ID = "1.0.1";
498    
499        static
500        {
501            setSystemProperty( "java.protocol.handler.pkgs", "net.dpml.transit" );
502            setSystemProperty( "java.util.logging.config.class", "net.dpml.util.ConfigurationHandler" );
503            setSystemProperty( "java.rmi.server.RMIClassLoaderSpi", "net.dpml.depot.DepotRMIClassLoaderSpi" );
504            setSystemProperty( Transit.SYSTEM_KEY, Transit.DPML_SYSTEM.getAbsolutePath() );
505            setSystemProperty( Transit.HOME_KEY, Transit.DPML_HOME.getAbsolutePath() );
506            setSystemProperty( Transit.DATA_KEY, Transit.DPML_DATA.getAbsolutePath() );
507            setSystemProperty( Transit.PREFS_KEY, Transit.DPML_PREFS.getAbsolutePath() );
508            setSystemProperty( BUILD_KEY, BUILD_ID );
509        }
510    
511        private static void setSystemProperty( String key, String value )
512        {
513            if( null == System.getProperty( key ) )
514            {
515                System.setProperty( key, value );
516            }
517        } 
518    
519        private static Logger m_LOGGER = null;
520        
521        private Command getCommand( String[] args )
522        {
523            String ref = getApplicationReference( args );
524            String app = System.getProperty( APPLICATION_KEY, ref );
525            return Command.parse( app );
526        }
527    
528        private String getApplicationReference( String[] args )
529        {
530            String key = "-D" + APPLICATION_KEY + "=";
531            for( int i=0; i<args.length; i++ )
532            {
533                String arg = args[i];
534                if( arg.startsWith( key ) )
535                {
536                    return arg.substring( 25 );
537                }
538            }
539            return null;
540        }
541    
542       /**
543        * Application selection key.
544        */
545        public static final String APPLICATION_KEY = "dpml.depot.application";
546        
547       /**
548        * Application identifier enumeration.
549        */
550        private static final class Command extends Enum
551        {
552            static final long serialVersionUID = 1L;
553    
554           /**
555            * Transit command id.
556            */
557            public static final Command TRANSIT = new Command( "dpml.transit" );
558    
559           /**
560            * Metro command id.
561            */
562            public static final Command METRO = new Command( "dpml.metro" );
563        
564           /**
565            * Station command id.
566            */
567            public static final Command STATION = new Command( "dpml.station" );
568        
569           /**
570            * Builder command id.
571            */
572            public static final Command BUILD = new Command( "dpml.builder" );
573        
574           /**
575            * Internal constructor.
576            * @param label the enumeration label.
577            */
578            private Command( String label )
579            {
580                super( label );
581            }
582            
583           /**
584            * Create a now mode using a supplied mode name.
585            * @param value the mode name
586            * @return the mode
587            * @exception NullPointerException if the supplied value is null
588            * @exception IllegalArgumentException if the supplied value is not recognized
589            */
590            public static Command parse( String value ) throws NullPointerException, IllegalArgumentException
591            {
592                if( null == value )
593                {
594                    final String error = 
595                      "Undefined sub-system identifier."
596                      + "\nThe depot cli handler must be supplied with an -D"
597                      + APPLICATION_KEY + "=[id] where id is one of the value 'dpml.metro', "
598                      + "'dpml.transit', 'dpml.station' or 'dpml.build'.";
599                    throw new NullPointerException( error ); 
600                }
601                if( value.equalsIgnoreCase( "dpml.metro" ) )
602                {
603                    return METRO;
604                }
605                else if( value.equalsIgnoreCase( "dpml.transit" ) )
606                {
607                    return TRANSIT;
608                }
609                else if( value.equalsIgnoreCase( "dpml.station" ) )
610                {
611                    return STATION;
612                }
613                else if( value.equalsIgnoreCase( "dpml.builder" ) )
614                {
615                    return BUILD;
616                }
617                else
618                {
619                    final String error =
620                      "Unrecognized application id [" + value + "]";
621                    throw new IllegalArgumentException( error );
622                }
623            }
624        }
625    }
626